HTTPS
HTTPS与HTTP没有任何的区别,HTTPS就是将HTTP协议数据包放到SSL/TSL层加密后,在TCP/IP层组成IP数据报去传输,以此保证传输数据的安全;而对于接收端,在SSL/TSL将接收的数据包解密之后,将数据传给HTTP协议层,就是普通的HTTP数据。HTTP和SSL/TSL都处于OSI模型的应用层。HTTPS连接建立过程大致是,客户端和服务端建立一个连接,服务端返回一个证书,客户端里存有各个受信任的证书机构根证书,用这些根证书对服务端返回的证书进行验证,经验证如果证书是可信任的,就生成一个pre-master secret,用这个证书的公钥加密后发送给服务端,服务端用私钥解密后得到pre-master secret,再根据某种算法生成master secret,客户端也同样根据这种算法从pre-master secret生成master secret,随后双方的通信都用这个master secret对传输数据进行加密解密。
SSL证书,有以下几种方式:
- 使用正规的商用授权证书,需要花钱的,据说也不算贵。四五十美金一年(自己没用过)。如果有这样的证书,AFNetworking不用任何额外的设置,只是在POST或者GET是使用的URL变成https开头的就可以了。
- 使用自己生成SSL证书:也就是使用无效证书。这种方式也比较普遍,尤其对中小企业和个人开发来讲,免费总是好的,而且灵活自如。随时可以更换证书。对于这种方式,AFNetworking也是支持的,但需要额外的几行配置代码才行。同时,这种方式有两种不同的连接方式:
2.1:证书不保存在手机客户端,客户端接收所有无效证书。
2.2:客户端只接受来自特定服务器端的证书。
SSL Pinning
可以理解为证书绑定,是指客户端直接保存服务端的证书,建立https连接时直接对比服务端返回的和客户端保存的两个证书是否一样,一样就表明证书是真的,不再去系统的信任证书机构里寻找验证。这适用于非浏览器应用,因为浏览器跟很多未知服务端打交道,无法把每个服务端的证书都保存到本地,但CS架构的像手机APP事先已经知道要进行通信的服务端,可以直接在客户端保存这个服务端的证书用于校验。
为什么直接对比就能保证证书没问题?如果中间人从客户端取出证书,再伪装成服务端跟其他客户端通信,它发送给客户端的这个证书不就能通过验证吗?确实可以通过验证,但后续的流程走不下去,因为下一步客户端会用证书里的公钥加密,中间人没有这个证书的私钥就解不出内容,也就截获不到数据,这个证书的私钥只有真正的服务端有,中间人伪造证书主要伪造的是公钥。
为什么要用SSL Pinning?正常的验证方式不够吗?如果服务端的证书是从受信任的的CA机构颁发的,验证是没问题的,但CA机构颁发证书比较昂贵,小企业或个人用户可能会选择自己颁发证书,这样就无法通过系统受信任的CA机构列表验证这个证书的真伪了,所以需要SSL Pinning这样的方式去验证.
NSURLConnection, NSURLSession实现支持HTTPS
先说下最常用的NSURLConnection支持HTTPS的实现(NSURLSession的实现方法类似,只是要求授权证明的回调不一样而已)后面讲说说怎样使用AFNetworking支持HTTPS
验证流程如下:
第一步,先获取需要验证的信任对象(Trust Object)。这个Trust Object在不同的应用场景下获取的方式都不一样,对于NSURLConnection来说,是从delegate方法-connection:willSendRequestForAuthenticationChallenge:回调回来的参数challenge中获取([challenge.protectionSpace serverTrust])。
使用系统默认验证方式验证Trust Object。SecTrustEvaluate会根据Trust Object的验证策略,一级一级往上,验证证书链上每一级数字签名的有效性(上一部分有讲解),从而评估证书的有效性。
如第二步验证通过了,一般的安全要求下,就可以直接验证通过,进入到下一步:使用Trust Object生成一份凭证([NSURLCredential credentialForTrust:serverTrust]),传入challenge的sender中([challenge.sender useCredential:cred forAuthenticationChallenge:challenge])处理,建立连接。
假如有更强的安全要求,可以继续对Trust Object进行更严格的验证。常用的方式是在本地导入证书,验证Trust Object与导入的证书是否匹配。更多的方法可以查看Enforcing Stricter Server Trust Evaluation,这一部分在讲解AFNetworking源码中会讲解到。
假如验证失败,取消此次Challenge-Response Authentication验证流程,拒绝连接请求。
如果采用自建证书则会跳过第二步,使用第三部进行验证,因为自建证书的根CA的数字签名未在操作系统的信任列表中
NSURLConnection中的代码实现:
|
|
上面是代码是通过系统默认验证流程来验证证书的。假如我们是自建证书的呢?这样Trust Object里面服务器的证书因为不是可信任的CA签发的,所以直接使用SecTrustEvaluate进行验证是不会成功。又或者,即使服务器返回的证书是信任CA签发的,又如何确定这证书就是我们想要的特定证书?这就需要先在本地导入证书,设置成需要验证的Anchor Certificate(就是根证书),再调用SecTrustEvaluate来验证。代码如下
|
|
AFNetworking支持HTTPS
AFNetworking已经将上面的逻辑代码封装好,甚至更完善,在AFSecurityPolicy文件中
AFSecurityPolicy
NSURLConnection已经封装了https连接的建立、数据的加密解密功能,我们直接使用NSURLConnection是可以访问https网站的,但NSURLConnection并没有验证证书是否合法,无法避免中间人攻击。要做到真正安全通讯,需要我们手动去验证服务端返回的证书,AFSecurityPolicy封装了证书验证的过程,让用户可以轻易使用,除了去系统信任CA机构列表验证,还支持SSL Pinning方式的验证。
AFSecurityPolicy分三种验证模式:
AFSSLPinningModeNone
这个模式表示不做SSL pinning,只跟浏览器一样在系统的信任机构列表里验证服务端返回的证书。若证书是信任机构签发的就会通过,若是自己服务器生成的证书,这里是不会通过的。AFSSLPinningModeCertificate
这个模式表示用证书绑定方式验证证书,需要客户端保存有服务端的证书拷贝,这里验证分两步,第一步验证证书的域名/有效期等信息,第二步是对比服务端返回的证书跟客户端返回的是否一致。
这里还没弄明白第一步的验证是怎么进行的,代码上跟去系统信任机构列表里验证一样调用了SecTrustEvaluate,只是这里的列表换成了客户端保存的那些证书列表。若要验证这个,是否应该把服务端证书的颁发机构根证书也放到客户端里?AFSSLPinningModePublicKey
这个模式同样是用证书绑定方式验证,客户端要有服务端的证书拷贝,只是验证时只验证证书里的公钥,不验证证书的有效期等信息。只要公钥是正确的,就能保证通信不会被窃听,因为中间人没有私钥,无法解开通过公钥加密的数据。1234567891011121314151617181920212223242526272829NSURL * url = [NSURL URLWithString:@"https://www.google.com"];AFHTTPRequestOperationManager * requestOperationManager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:url];dispatch_queue_t requestQueue = dispatch_create_serial_queue_for_name("kRequestCompletionQueue");requestOperationManager.completionQueue = requestQueue;//验证模式 AFSSLPinningModeCertificateAFSecurityPolicy * securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];//allowInvalidCertificates 是否允许无效证书(也就是自建的证书),默认为NO//如果是需要验证自建证书,需要设置为YESsecurityPolicy.allowInvalidCertificates = YES;//validatesDomainName 是否需要验证域名,默认为YES;//假如证书的域名与你请求的域名不一致,需把该项设置为NO//主要用于这种情况:客户端请求的是子域名,而证书上的是另外一个域名。因为SSL证书上的域名是独立的,假如证书上注册的域名是www.google.com,那么mail.google.com是无法验证通过的;当然,有钱可以注册通配符的域名*.google.com,但这个还是比较贵的。securityPolicy.validatesDomainName = NO;//validatesCertificateChain 是否验证整个证书链,默认为YES//设置为YES,会将服务器返回的Trust Object上的证书链与本地导入的证书进行对比,这就意味着,假如你的证书链是这样的://GeoTrust Global CA// Google Internet Authority G2// *.google.com//那么,除了导入*.google.com之外,还需要导入证书链上所有的CA证书(GeoTrust Global CA, Google Internet Authority G2);//如是自建证书的时候,可以设置为YES,增强安全性;假如是信任的CA所签发的证书,则建议关闭该验证;securityPolicy.validatesCertificateChain = NO;requestOperationManager.securityPolicy = securityPolicy;
例子:
参考 AFNetworking2.0源码解
http://http://blog.cnbang.net/tech
参考 iOS安全系列之一:HTTPS
http://oncenote.com/2014/10/21/Security-1-HTTPS/
参考 iOS https(SSL/TLS)数据捕获
http://www.h4ck.org.cn/2013/08/ios-httpsssltls%E6%95%B0%E6%8D%AE%E6%8D%95%E8%8E%B7/